package org.knowm.xchange.gateio.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.Collection;
import java.util.Date;
import org.junit.jupiter.api.Test;
import org.knowm.xchange.currency.Currency;
import org.knowm.xchange.currency.CurrencyPair;
import org.knowm.xchange.dto.Order;
import org.knowm.xchange.dto.Order.OrderStatus;
import org.knowm.xchange.dto.Order.OrderType;
import org.knowm.xchange.dto.trade.LimitOrder;
import org.knowm.xchange.dto.trade.MarketOrder;
import org.knowm.xchange.dto.trade.OpenOrders;
import org.knowm.xchange.dto.trade.UserTrade;
import org.knowm.xchange.dto.trade.UserTrades;
import org.knowm.xchange.exceptions.FundsExceededException;
import org.knowm.xchange.gateio.GateioExchangeWiremock;
import org.knowm.xchange.gateio.dto.trade.GateioUserTrade;
import org.knowm.xchange.gateio.dto.trade.Role;
import org.knowm.xchange.gateio.service.params.GateioTradeHistoryParams;
import org.knowm.xchange.service.trade.params.DefaultCancelOrderByInstrumentAndIdParams;
import org.knowm.xchange.service.trade.params.orders.DefaultOpenOrdersParamInstrument;
import org.knowm.xchange.service.trade.params.orders.DefaultQueryOrderParamInstrument;

class GateioTradeServiceTest extends GateioExchangeWiremock {

  GateioTradeService gateioTradeService = (GateioTradeService) exchange.getTradeService();

  @Test
  void place_order_not_enough_balance() {
    MarketOrder marketOrder =
        new MarketOrder.Builder(OrderType.BID, CurrencyPair.BTC_USDT)
            .userReference("t-balance-test")
            .originalAmount(BigDecimal.valueOf(100))
            .build();

    assertThatExceptionOfType(FundsExceededException.class)
        .isThrownBy(() -> gateioTradeService.placeMarketOrder(marketOrder));
  }

  @Test
  void valid_market_buy_order() throws IOException {
    MarketOrder marketOrder =
        new MarketOrder.Builder(OrderType.BID, CurrencyPair.BTC_USDT)
            .userReference("t-valid-market-buy-order")
            .originalAmount(BigDecimal.valueOf(20))
            .build();

    String actualResponse = gateioTradeService.placeMarketOrder(marketOrder);
    assertThat(actualResponse).isEqualTo("342251629898");
  }

  @Test
  void valid_market_sell_order() throws IOException {
    MarketOrder marketOrder =
        new MarketOrder.Builder(OrderType.ASK, CurrencyPair.BTC_USDT)
            .userReference("t-valid-market-sell-order")
            .originalAmount(new BigDecimal("0.0007"))
            .build();

    String actualResponse = gateioTradeService.placeMarketOrder(marketOrder);
    assertThat(actualResponse).isEqualTo("342260949533");
  }

  @Test
  void valid_limit_sell_order() throws IOException {
    LimitOrder limitOrder =
        new LimitOrder.Builder(OrderType.ASK, CurrencyPair.BTC_USDT)
            .userReference("t-valid-limit-sell-order")
            .originalAmount(new BigDecimal("0.00068"))
            .limitPrice(new BigDecimal("29240.7"))
            .build();

    String actualResponse = gateioTradeService.placeLimitOrder(limitOrder);
    assertThat(actualResponse).isEqualTo("373824296029");
  }

  @Test
  void valid_cancel_order() throws IOException {
    boolean actual =
        gateioTradeService.cancelOrder(
            new DefaultCancelOrderByInstrumentAndIdParams(CurrencyPair.BTC_USDT, "376835979523"));
    assertThat(actual).isTrue();
  }

  @Test
  void valid_limit_buy_order() throws IOException {
    LimitOrder limitOrder =
        new LimitOrder.Builder(OrderType.BID, CurrencyPair.BTC_USDT)
            .userReference("t-valid-limit-buy-order")
            .originalAmount(new BigDecimal("0.00068"))
            .limitPrice(new BigDecimal("10000.7"))
            .build();

    String actualResponse = gateioTradeService.placeLimitOrder(limitOrder);
    assertThat(actualResponse).isEqualTo("376835979523");
  }

  @Test
  void buy_order_details() throws IOException {
    MarketOrder expected =
        new MarketOrder.Builder(OrderType.BID, CurrencyPair.BTC_USDT)
            .id("342251629898")
            .userReference("t-valid-market-buy-order")
            .timestamp(Date.from(Instant.parse("2023-06-03T22:07:38.451Z")))
            .originalAmount(BigDecimal.valueOf(20))
            .orderStatus(OrderStatus.FILLED)
            .cumulativeAmount(new BigDecimal("18.92681"))
            .averagePrice(new BigDecimal("27038.3"))
            .fee(new BigDecimal("0.0000014"))
            .build();

    Collection<Order> orders =
        gateioTradeService.getOrder(
            new DefaultQueryOrderParamInstrument(CurrencyPair.BTC_USDT, "342251629898"));
    assertThat(orders).hasSize(1);
    assertThat(orders).first().usingRecursiveComparison().isEqualTo(expected);
  }

  @Test
  void sell_order_details() throws IOException {
    MarketOrder expected =
        new MarketOrder.Builder(OrderType.ASK, new CurrencyPair("VAI/USDT"))
            .id("425539509181")
            .userReference("t-valid-market-sell-order")
            .timestamp(Date.from(Instant.parse("2023-10-28T18:40:02.006Z")))
            .originalAmount(new BigDecimal("105.58"))
            .orderStatus(OrderStatus.FILLED)
            .cumulativeAmount(new BigDecimal("105.58"))
            .averagePrice(new BigDecimal("0.079888"))
            .fee(new BigDecimal("0.01686915008"))
            .build();

    Collection<Order> orders =
        gateioTradeService.getOrder(
            new DefaultQueryOrderParamInstrument(new CurrencyPair("VAI/USDT"), "425539509181"));
    assertThat(orders).hasSize(1);
    assertThat(orders).first().usingRecursiveComparison().isEqualTo(expected);
  }

  @Test
  void sell_order_partially_filled_details() throws IOException {
    MarketOrder expected =
        new MarketOrder.Builder(OrderType.ASK, new CurrencyPair("FREE/USDT"))
            .id("874190804193")
            .userReference("t-valid-partially-filled-order")
            .timestamp(Date.from(Instant.parse("2025-07-13T09:11:47.335Z")))
            .originalAmount(new BigDecimal("589107410.1"))
            .orderStatus(OrderStatus.PARTIALLY_FILLED)
            .cumulativeAmount(new BigDecimal("183488100"))
            .averagePrice(new BigDecimal("0.00000004614"))
            .fee(new BigDecimal("0.008466141815984"))
            .build();

    Collection<Order> orders =
        gateioTradeService.getOrder(
            new DefaultQueryOrderParamInstrument(new CurrencyPair("FREE/USDT"), "874190804193"));
    assertThat(orders).hasSize(1);
    assertThat(orders)
        .first()
        .usingComparatorForType(BigDecimal::compareTo, BigDecimal.class)
        .usingRecursiveComparison()
        .isEqualTo(expected);
  }

  @Test
  void open_limit_order_details() throws IOException {
    LimitOrder expected =
        new LimitOrder.Builder(OrderType.BID, CurrencyPair.BTC_USDT)
            .id("745504484392")
            .limitPrice(new BigDecimal("80000"))
            .timestamp(Date.from(Instant.parse("2024-12-05T23:46:54.447Z")))
            .originalAmount(new BigDecimal("0.00012"))
            .orderStatus(OrderStatus.OPEN)
            .fee(BigDecimal.ZERO)
            .userReference("web")
            .build();

    Collection<Order> orders =
        gateioTradeService.getOrder(
            new DefaultQueryOrderParamInstrument(CurrencyPair.BTC_USDT, "745504484392"));
    assertThat(orders).hasSize(1);
    assertThat(orders).first().usingRecursiveComparison().isEqualTo(expected);
  }

  @Test
  void open_orders() throws IOException {
    LimitOrder expected =
        new LimitOrder.Builder(OrderType.BID, CurrencyPair.BTC_USDT)
            .id("745504484392")
            .limitPrice(new BigDecimal("80000"))
            .timestamp(Date.from(Instant.parse("2024-12-05T23:46:54.447Z")))
            .originalAmount(new BigDecimal("0.00012"))
            .orderStatus(OrderStatus.OPEN)
            .fee(BigDecimal.ZERO)
            .userReference("web")
            .build();

    OpenOrders openOrders =
        gateioTradeService.getOpenOrders(
            new DefaultOpenOrdersParamInstrument(CurrencyPair.BTC_USDT));
    assertThat(openOrders.getOpenOrders()).hasSize(1);
    assertThat(openOrders.getOpenOrders()).first().usingRecursiveComparison().isEqualTo(expected);
  }

  @Test
  void trade_history() throws IOException {
    UserTrades userTrades =
        gateioTradeService.getTradeHistory(
            GateioTradeHistoryParams.builder()
                .currencyPair(CurrencyPair.BTC_USDT)
                .pageLength(2)
                .pageNumber(2)
                .startTime(Date.from(Instant.ofEpochSecond(1691617924)))
                .endTime(Date.from(Instant.ofEpochSecond(1691704324)))
                .build());

    assertThat(userTrades.getUserTrades()).hasSize(2);

    GateioUserTrade expected =
        GateioUserTrade.builder()
            .type(OrderType.ASK)
            .originalAmount(new BigDecimal("0.00005"))
            .instrument(CurrencyPair.BTC_USDT)
            .price(new BigDecimal("29447.2"))
            .timestamp(Date.from(Instant.ofEpochMilli(1691702286356L)))
            .id("6068789332")
            .orderId("381064942553")
            .feeAmount(new BigDecimal("0.00294472"))
            .feeCurrency(Currency.USDT)
            .orderUserReference("-")
            .role(Role.TAKER)
            .build();

    UserTrade actual = userTrades.getUserTrades().get(0);

    assertThat(actual).usingRecursiveComparison().isEqualTo(expected);
  }
}
